package cn.edu.dgut.css.sai.course.wechatoauth2demo.config;

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
import org.springframework.util.Assert;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

/**
 * 自定义 OAuth2UserService
 *
 * <li>参考文档：https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html</li>
 *
 * @author sai
 * @see DefaultOAuth2UserService
 * @see OAuth2UserRequest
 * @see OAuth2AccessToken
 * @since 2020/12/26
 */
public final class WeChatOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {

    private final RestTemplateBuilder restTemplateBuilder;
    private final WeChatOAuth2ClientProperties weChatOAuth2ClientProperties;

    public WeChatOAuth2UserService(RestTemplateBuilder restTemplateBuilder, WeChatOAuth2ClientProperties weChatOAuth2ClientProperties) {
        this.restTemplateBuilder = restTemplateBuilder;
        this.weChatOAuth2ClientProperties = weChatOAuth2ClientProperties;
    }

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        String accessToken = userRequest.getAccessToken().getTokenValue();
        String openid = userRequest.getAdditionalParameters().get("openid").toString();

        // 1、获取微信用户信息
        Map<String, Object> userInfo = getUserInfo(accessToken, openid);

        // 2. 构造 Oauth2User
        return createOAuth2User(userInfo, userRequest);
    }

    private OAuth2User createOAuth2User(Map<String, Object> userInfo, OAuth2UserRequest userRequest) {
        // 先构建一个OAuth2UserAuthority，默认有ROLE_USER 。
        Set<GrantedAuthority> authorities = new LinkedHashSet<>();
        authorities.add(new OAuth2UserAuthority(userInfo));

        // 从token获取scope，并构建 SCOPE_ 权限。
        OAuth2AccessToken token = userRequest.getAccessToken();
        for (String authority : token.getScopes()) {
            authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority));
        }

        // 构建 DefaultOAuth2User 对象。
        return new DefaultOAuth2User(authorities, userInfo, "openid");
    }

    private Map<String, Object> getUserInfo(String accessToken, String openid) {
        // @formatter:off
        URI uri = UriComponentsBuilder.fromUriString(weChatOAuth2ClientProperties.getUserInfoUrl())
                                        .queryParam("access_token", accessToken)
                                        .queryParam("openid", openid)
                                        .queryParam("lang", Locale.getDefault())
                                        .encode()
                                        .build()
                                        .toUri();
        ResponseEntity<Map<String, Object>> responseEntity = restTemplateBuilder.build().exchange(RequestEntity.get(uri).build(), new ParameterizedTypeReference<>() {});
        // @formatter:on

        Map<String, Object> userInfoResponsBody = responseEntity.getBody();
        Assert.notNull(userInfoResponsBody, "userInfoResponsBody 不能为 null");

        return userInfoResponsBody;
    }
}
